/* Software Name : AsmDex
 * Version : 1.0
 *
 * Copyright © 2012 France Télécom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.ow2.asmdex.structureWriter;

import org.ow2.asmdex.Opcodes;
import org.ow2.asmdex.encodedValue.EncodedValue;
import org.ow2.asmdex.encodedValue.EncodedValueFactory;
import org.ow2.asmdex.encodedValue.EncodedValueString;
import org.ow2.asmdex.encodedValue.EncodedValueUtil;

/**
 * Class representing a Field. A Field, as described by the field_id_item format, consists in its name,
 * the name of the class owning it, and its type. However, contrary to the field_id_item, it also own
 * its access flags, and its value.
 * 
 * The value is only used for Final Static fields, else it's Null.
 * 
 * Having two fields equals does NOT mean their value is equal. Just that the name, owner and type are the
 * same.
 * 
 * Implements Comparable in order to easily sort the Fields. Like requested by the Dex documentation,
 * two Fields are sorted according to the class owning type, the field name and then their own type.
 * 
 * @author Julien Névo
 */
public class Field implements Comparable<Field>, IAnnotationsHolder {

	/**
	 * Name of the Class, owner of the Field.
	 */
	final private String className;
	
	/**
	 * Type of the Field, described in the TypeDescriptor format.
	 */
	final private String typeName;
	
	/**
	 * Name of the Field.
	 */
	final private String fieldName;
	
	/**
	 * Access flags of the Field.
	 */
	private int access;
	
	/**
	 * The Signature of the Field. May be Null.
	 */
	private String[] signature;
	
	/**
	 * Value of the Field. Null for non-final static fields.
	 */
	private EncodedValue value = null;

	/**
	 * Annotation_set_item, representing all the annotation_items for this Field.
	 */
	private AnnotationSetItem annotationSetItem = new AnnotationSetItem();
	
	/**
	 * The hashcode of the Field, calculated in the Constructor.
	 */
	final private int hashcode;
	
	/**
	 * Constructor of the Field.
	 * @param fieldName name of the Field.
	 * @param desc type of the Field, described in the TypeDescriptor format.
	 * @param classOwningName name of the Class, owner of the Field.
	 */
	public Field(String fieldName, String desc, String classOwningName) {
		this.fieldName = fieldName;
		typeName = desc;
		className = classOwningName;
		hashcode = calculateHashCode(fieldName, className, typeName);
	}
	
	/**
	 * Init if this is the one kept.
	 * @param access the access flags of the Field.
	 * @param signature the Signature of the field. May be Null.
	 * @param value the value of the Field, or Null for non-static Fields.
	 * @param constantPool the Constant Pool.
	 */
	public void init( int access, String[] signature, Object value, ConstantPool constantPool) {
		this.access = access;
		this.signature = signature;
		setEncodedValueFromObject(value, typeName, constantPool);
	}

	
	// ----------------------------------------------
	// Private methods.
	// ----------------------------------------------
	
	/**
	 * Sets the value field from the given Object.
	 * @param value the Object.
	 * @param desc the descriptor of the Field, described in the TypeDescriptor format. It is useful to know
	 *        if the Object contains a reference of not.
	 * @param constantPool the Constant Pool of the Application.
	 */
	private void setEncodedValueFromObject(Object value, String desc, ConstantPool constantPool) {
		if ((access & (Opcodes.ACC_STATIC)) > 0) {
			if (value != null) {
				// Reference values are always encoded as Null, even if the value is given.
				// Exception for the Strings, which are encoded.
				boolean isRef = EncodedValueUtil.isTypeAReference(desc);
				if (isRef) {
					if (EncodedValueUtil.isString(desc)) {
						String str = (String)value;
						this.value = new EncodedValueString(str);
						constantPool.addStringToConstantPool(str);
					} else {
						setNoValue();
					}
				} else {
					this.value = EncodedValueFactory.getEncodedValue(value, desc);
				}
			}
		}
	}


	// ----------------------------------------------
	// Public methods.
	// ----------------------------------------------

	/**
	 * Calculates the hashcode of a Field according to some of its attributes.
	 * @param fieldName the name of the Field.
	 * @param classOwningName the Class owning the Field.
	 * @param desc type of the Field, described in the TypeDescriptor format.
	 * @return the hashcode of a Field.
	 */
	public static int calculateHashCode(String fieldName, String classOwningName, String desc) {
		return classOwningName.hashCode() + desc.hashCode() + fieldName.hashCode();
	}
	
	/**
	 * Adds information to the current Field. This is useful <i>only</i> if this Field has the ACC_UNKNOWN
	 * flag, which means Instructions referred to it while it has not yet been visited,
	 * declaring it but not giving all the information it should have.
	 */
	public void completeInformation(int access, String[] signature, Object value, ConstantPool constantPool) {
		this.access = access;
		this.signature = signature;
		setEncodedValueFromObject(value, typeName, constantPool);
	}
	
	/**
	 * Indicates whether the Field is Static.
	 */
	public boolean isStatic() {
		return (access & Opcodes.ACC_STATIC) > 0;
	}
	
	/**
	 * Indicates whether the Field is Final and Static.
	 */
	public boolean isFinalStatic() {
		return ((access & Opcodes.ACC_STATIC) > 0) && ((access & Opcodes.ACC_FINAL) > 0);
	}
	
	/**
	 * Indicates whether the Field is unknown (referred to, but not yet parsed).
	 * @return true if the Field is unknown.
	 */
	public boolean isUnknown() {
		return (access & Opcodes.ACC_UNKNOWN) != 0;
	}
	
	/**
	 * Adds an annotation_item to the annotations_set_items.
	 * @param annotationItem the Annotation Item to add.
	 */
	public void addAnnotationItem(AnnotationItem annotationItem) {
		annotationSetItem.addAnnotationItem(annotationItem);
	}
	
	// ----------------------------------------------
	// Getters.
	// ----------------------------------------------
	
	/**
	 * Returns the Class owning the Field.
	 * @return the Class owning the Field.
	 */
	public String getClassName() {
		return className;
	}

	/**
	 * Returns the Type of the Field, described in the TypeDescriptor format.
	 * @return the Type of the Field, described in the TypeDescriptor format.
	 */
	public String getTypeName() {
		return typeName;
	}

	/**
	 * Returns the Name of the Field.
	 * @return the Name of the Field.
	 */
	public String getFieldName() {
		return fieldName;
	}
	
	/**
	 * Returns the access flags of the Field.
	 * @return the access flags of the Field.
	 */
	public int getAccess() {
		return access;
	}
	
	/**
	 * Returns the Signature of the Field. May be Null.
	 * @return the Signature of the Field. May be Null.
	 */
	public String[] getSignature() {
		return signature;
	}
	
	/**
	 * Returns the encoded value of the field. Null for non-static fields. 
	 * @return the encoded value of the field, or Null.
	 */
	public EncodedValue getValue() {
		return value;
	}
	
	/**
	 * Encode the value into an array of Bytes.
	 * @return an array of bytes.
	 */
	public byte[] encodeValue(ConstantPool constantPool) {
		return (value == null) ? null : value.encode(constantPool);
	}
	
	/**
	 * Sets the value to 0 or Null according to its type.
	 */
	public void setNoValue() {
		value = EncodedValueFactory.getEncodedEmptyValue(typeName);
	}
	
	/**
	 * Returns the annotation_set_item this structure currently contains.
	 * @return the annotation_set_item this structure currently contains.
	 */
	@Override
	public AnnotationSetItem getAnnotationSetItem() {
		return annotationSetItem;
	}

	/**
	 * Returns the number of annotation_items this structure currently contains.
	 * @return the number of annotation_items this structure currently contains.
	 */
	@Override
	public int getNbAnnotations() {
		return annotationSetItem.getNbAnnotationItems();
	}
	
	
	// ----------------------------------------------
	// Overridden methods.
	// ----------------------------------------------

	@Override
	public int hashCode() {
		return hashcode;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}

		if (obj instanceof Field) {
			Field field = (Field)obj;
			return fieldName.equals(field.fieldName) && 
				(className.equals(field.className) && (typeName.equals(field.typeName)));
		}
		
		return false;
	}
	
	@Override
	public int compareTo(Field field) {
		if (this == field) {
			return 0;
		}
		
		// Tests class owning name first.
		int compare = className.compareTo(field.className); 
		if (compare != 0) {
			return compare;
		}
		
		// Tests the names.
		compare = fieldName.compareTo(field.fieldName);
		if (compare != 0) {
			return compare;
		}
		
		// Tests the type.
		return typeName.compareTo(field.typeName);
	}

}